
#include "TimeZone.h"

using namespace Riccsson::System;

GC_PTR<TimeZone> TimeZone::currentTimeZone;

GC_PTR<object> TimeZone::tz_lock;
long long TimeZone::timezone_check;

PROP3GET_STATIC_INIT(TimeZone, GC_PTR<TimeZone>, CurrentTimeZone)

REGISTER_CLASS_CPP(TimeZone)
{
	tz_lock = new object ();
	timezone_check = 0;
}

TimeZone::TimeZone()
	: PROP3GET_INIT(TimeZone, DaylightName)
	, PROP3GET_INIT(TimeZone, StandardName)
{
}

PROP3_GET_CPP(TimeZone, GC_PTR<TimeZone>, CurrentTimeZone)
{
	long long now = DateTime::GetNow ();
	GC_PTR<TimeZone> tz;
				
	lock (tz_lock)
	{
		if (currentTimeZone == null || Math::Abs (now - timezone_check) > TimeSpan::TicksPerMinute) 
		{
			currentTimeZone = new CurrentSystemTimeZone (now);
			timezone_check = now;
		}
					
		tz = currentTimeZone;
	}
				
	return tz;
}

bool TimeZone::IsDaylightSavingTime (DateTime time)
{
	return IsDaylightSavingTime (time, GetDaylightChanges (time.Year));
}

bool TimeZone::IsDaylightSavingTime (DateTime time, Globalization::DaylightTime* daylightTimes)
{
	if (daylightTimes == null)
		throw ArgumentNullException ("daylightTimes");

	// If Start == End, then DST is off
	if (daylightTimes->Start.get().Ticks == daylightTimes->End.get().Ticks)
		return false;

	//We are in the northern hemisphere.
	if (daylightTimes->Start.get().Ticks < daylightTimes->End.get().Ticks) {
		if (daylightTimes->Start.get().Ticks < time.Ticks && daylightTimes->End.get().Ticks > time.Ticks)
			return true; // time lies between Start and End

	}
	else {  // We are in the southern hemisphere.
		if (time.Year == daylightTimes->Start.get().Year && time.Year == daylightTimes->End.get().Year)
			if (time.Ticks < daylightTimes->End.get().Ticks || time.Ticks > daylightTimes->Start.get().Ticks)
				return true; // time is less than End OR more than Start 
	}

	return false;
}

DateTime TimeZone::ToLocalTime (DateTime time)
{
	if (time.Kind == DateTimeKind::Local)
		return time;

	TimeSpan utcOffset = GetUtcOffset (DateTime (time.Ticks));
	if (utcOffset.Ticks > 0) {
		if (DateTime::MaxValue - utcOffset < time)
			return DateTime::SpecifyKind (DateTime::MaxValue, DateTimeKind::Local);
	} else if (utcOffset.Ticks < 0) {
		if (time.Ticks + utcOffset.Ticks < DateTime::MinValue.Ticks)
			return DateTime::SpecifyKind (DateTime::MinValue, DateTimeKind::Local);
	}

	DateTime local = DateTime::SpecifyKind (time.Add (utcOffset), DateTimeKind::Local);
	GC_PTR<Globalization::DaylightTime> dlt = GetDaylightChanges (time.Year);
	if (dlt->Delta.get().Ticks == 0)
		return DateTime::SpecifyKind (local, DateTimeKind::Local);

	// FIXME: check all of the combination of
	//	- basis: local-based or UTC-based
	//	- hemisphere: Northern or Southern
	//	- offset: positive or negative

	// PST should work fine here.
	if (local < dlt->End && dlt->End.get().Subtract (dlt->Delta) <= local)
		return DateTime::SpecifyKind (local, DateTimeKind::Local);

	TimeSpan localOffset = GetUtcOffset (local);
	return DateTime::SpecifyKind (time.Add (localOffset), DateTimeKind::Local);
}

DateTime TimeZone::ToUniversalTime (DateTime time)
{
	if (time.Kind == DateTimeKind::Utc)
		return time;

	TimeSpan offset = GetUtcOffset (time);

	if (offset.Ticks < 0) {
		if (DateTime::MaxValue + offset < time)
			return DateTime::SpecifyKind (DateTime::MaxValue, DateTimeKind::Utc);
	} else if (offset.Ticks > 0) {
		if (DateTime::MinValue + offset > time)
			return DateTime::SpecifyKind (DateTime::MinValue, DateTimeKind::Utc);
	}

	return DateTime::SpecifyKind (DateTime (time.Ticks - offset.Ticks), DateTimeKind::Utc);
}

TimeSpan TimeZone::GetLocalTimeDiff (DateTime time)
{
	return GetLocalTimeDiff (time, GetUtcOffset (time));
}

TimeSpan TimeZone::GetLocalTimeDiff (DateTime time, TimeSpan utc_offset)
{
	GC_PTR<Globalization::DaylightTime> dlt = GetDaylightChanges (time.Year);

	if (dlt->Delta.get().Ticks == 0)
		return utc_offset;

	DateTime local = time.Add (utc_offset);
	if (local < dlt->End && dlt->End.get().Subtract (dlt->Delta) <= local)
		return utc_offset;

	if (local >= dlt->Start && dlt->Start.get().Add (dlt->Delta) > local)
		return utc_offset - dlt->Delta;

	return GetUtcOffset (local);
}

//RUN_STATIC(TimeZone);







int CurrentSystemTimeZone::this_year;
GC_PTR<Globalization::DaylightTime> CurrentSystemTimeZone::this_year_dlt;
